﻿using PMS.Core.Caching;
using PMS.Core.Configuration.CommonEnumConfigs;
using PMS.Core.Utils;
using PMS.Data.DbUtils;
using PMS.Data.Entities.Common;
using PMS.Data.Entities.Customers;
using PMS.Data.Entities.Settings;
using PMS.Services.Caching;
using PMS.Services.Caching.Extensions;
using PMS.Services.EntityServices.Common;
using PMS.Services.SharedKernel;
using PMS.Services.Utils;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

namespace PMS.Services.EntityServices.Customers
{
    /// <summary>
    /// Customer service
    /// </summary>
    public partial class CustomerService : ICustomerService
    {
        #region Fields

        private readonly CachingSettings _cachingSettings;
        private readonly CustomerSettings _customerSettings;
        private readonly ICacheKeyService _cacheKeyService;
        private readonly IEventDispatcher _eventDispatcher;
        private readonly IGenericAttributeService _genericAttributeService;
        //private readonly IRepository<Address> _customerAddressRepository;
        private readonly IRepository<Customer> _customerRepository;
        //private readonly IRepository<CustomerAddressMapping> _customerAddressMappingRepository;
        private readonly IRepository<CustomerCustomerRoleMapping> _customerCustomerRoleMappingRepository;
        private readonly IRepository<CustomerPassword> _customerPasswordRepository;
        private readonly IRepository<CustomerRole> _customerRoleRepository;
        private readonly IRepository<GenericAttribute> _gaRepository;
        //private readonly IRepository<ShoppingCartItem> _shoppingCartRepository;
        private readonly IRepository<Organize> _organizeRepository;
        private readonly IStaticCacheManager _staticCacheManager;
        private readonly IStoreContext _storeContext;
        //private readonly ShoppingCartSettings _shoppingCartSettings;

        #endregion

        #region Ctor

        public CustomerService(CachingSettings cachingSettings,
            CustomerSettings customerSettings,
            ICacheKeyService cacheKeyService,
            IEventDispatcher eventDispatcher,
            IGenericAttributeService genericAttributeService,
            //IRepository<Address> customerAddressRepository,
            IRepository<Customer> customerRepository,
            //IRepository<CustomerAddressMapping> customerAddressMappingRepository,
            IRepository<CustomerCustomerRoleMapping> customerCustomerRoleMappingRepository,
            IRepository<CustomerPassword> customerPasswordRepository,
            IRepository<CustomerRole> customerRoleRepository,
            IRepository<GenericAttribute> gaRepository,
            //IRepository<ShoppingCartItem> shoppingCartRepository,
            IRepository<Organize> organizeRepository,
            IStaticCacheManager staticCacheManager,
            IStoreContext storeContext)
            //ShoppingCartSettings shoppingCartSettings)
        {
            _cachingSettings = cachingSettings;
            _customerSettings = customerSettings;
            _cacheKeyService = cacheKeyService;
            _eventDispatcher = eventDispatcher;
            _genericAttributeService = genericAttributeService;
            //_customerAddressRepository = customerAddressRepository;
            _customerRepository = customerRepository;
            //_customerAddressMappingRepository = customerAddressMappingRepository;
            _customerCustomerRoleMappingRepository = customerCustomerRoleMappingRepository;
            _customerPasswordRepository = customerPasswordRepository;
            _customerRoleRepository = customerRoleRepository;
            _gaRepository = gaRepository;
            //_shoppingCartRepository = shoppingCartRepository;
            _organizeRepository = organizeRepository;
            _staticCacheManager = staticCacheManager;
            _storeContext = storeContext;
            //_shoppingCartSettings = shoppingCartSettings;
        }

        #endregion

        #region Methods

        #region Customers

        /// <summary>
        /// Gets all customers
        /// </summary>
        /// <param name="createdFromUtc">Created date from (UTC); null to load all records</param>
        /// <param name="createdToUtc">Created date to (UTC); null to load all records</param>
        /// <param name="customerRoleIds">A list of customer role identifiers to filter by (at least one match); pass null or empty list in order to load all customers; </param>
        /// <param name="email">Email; null to load all customers</param>
        /// <param name="loginname">Loginname; null to load all customers</param>
        /// <param name="mobile">PhoneNumber; null to load all customers</param>
        /// <param name="nickname">User NickName/Displayname; null to load all customers</param>
        /// <param name="dayOfBirth">Day of birth; 0 to load all customers</param>
        /// <param name="monthOfBirth">Month of birth; 0 to load all customers</param>
        /// <param name="pageIndex">Page index</param>
        /// <param name="pageSize">Page size</param>
        /// <param name="getOnlyTotalCount">A value in indicating whether you want to load only total number of records. Set to "true" if you don't want to load data from database</param>
        /// <returns>Customers</returns>
        public virtual IPagedList<Customer> GetAllCustomers(DateTime? createdFromUtc = null, DateTime? createdToUtc = null,
            int[] customerRoleIds = null, string email = null, string loginname = null, string mobile = null,
            string nickname = null, int dayOfBirth = 0, int monthOfBirth = 0,
            int pageIndex = 0, int pageSize = int.MaxValue, bool getOnlyTotalCount = false)
        {
            var query = _customerRepository.Table;
            if (createdFromUtc.HasValue)
                query = query.Where(c => createdFromUtc.Value <= c.CreatedOnTimeUtc);
            if (createdToUtc.HasValue)
                query = query.Where(c => createdToUtc.Value >= c.CreatedOnTimeUtc);

            query = query.Where(c => !c.IsDeleted);

            if(customerRoleIds != null && customerRoleIds.Contains(0))
            {//查询未激活用户
                /* Solution 1:
                 * SELECT u.* 
                 * FROM bus_customer u
                 * WHERE u.IsDeleted = 0
                 * AND u.IsActive = 1
                 * AND (SELECT count(1) AS num FROM customer_customerrole_mapping AS m WHERE m.Customer_Id = u.Id) = 0;
                 * 
                 * Solution 2:
                 * SELECT u.*
                 * FROM	bus_customer u
                 * LEFT JOIN customer_customerrole_mapping m ON u.Id = m.Customer_Id
                 * WHERE u.IsDeleted = 0
                 * AND u.IsActive = 1
                 * AND m.Customer_Id IS NULL;
                 */
                query = (from u in query
                         join m in _customerCustomerRoleMappingRepository.Table
                         on u.Id equals m.CustomerId into tmpMapping
                         from x in tmpMapping.DefaultIfEmpty()
                         where x.CustomerId.Equals(null)
                         select u).Distinct();
            }
            else if (customerRoleIds != null && customerRoleIds.Length > 0)
            {
                query = query.Join(_customerCustomerRoleMappingRepository.Table, x => x.Id, y => y.CustomerId,
                        (x, y) => new { Customer = x, Mapping = y })
                    .Where(z => customerRoleIds.Contains(z.Mapping.CustomerRoleId))
                    .Select(z => z.Customer)
                    .Distinct();
            } 
            if (!string.IsNullOrWhiteSpace(email))
                query = query.Where(c => c.Email.Contains(email));
            if (!string.IsNullOrWhiteSpace(loginname))
                query = query.Where(c => c.LoginName.Contains(loginname));
            if (!string.IsNullOrWhiteSpace(mobile))
                query = query.Where(c => c.Mobile.Contains(mobile));
            if (!string.IsNullOrWhiteSpace(nickname))
                query = query.Where(c => c.NickName.Contains(nickname));

            //date of birth is stored as a string into database.
            //we also know that date of birth is stored in the following format YYYY-MM-DD (for example, 1983-02-18).
            //so let's search it as a string
            //此功能可用于快速查询本日过生日所有用户，并批量发送生日祝福邮件
            if (dayOfBirth > 0 && monthOfBirth > 0)
            {
                //both are specified
                var dateOfBirthStr = monthOfBirth.ToString("00", CultureInfo.InvariantCulture) + "-" + dayOfBirth.ToString("00", CultureInfo.InvariantCulture);

                //z.Attribute.Value.Length - dateOfBirthStr.Length = 5
                //dateOfBirthStr.Length = 5
                query = query
                    .Join(_gaRepository.Table, x => x.Id, y => y.EntityId, (x, y) => new { Customer = x, Attribute = y })
                    .Where(z => z.Attribute.KeyGroup == nameof(Customer) &&
                                z.Attribute.Key == CustomerDefaultConfigs.DateOfBirthAttribute &&
                                z.Attribute.Value.Substring(5, 5) == dateOfBirthStr)
                    .Select(z => z.Customer);
            }
            else if (dayOfBirth > 0)
            {
                //only day is specified
                var dateOfBirthStr = dayOfBirth.ToString("00", CultureInfo.InvariantCulture);

                //z.Attribute.Value.Length - dateOfBirthStr.Length = 8
                //dateOfBirthStr.Length = 2
                query = query
                    .Join(_gaRepository.Table, x => x.Id, y => y.EntityId, (x, y) => new { Customer = x, Attribute = y })
                    .Where(z => z.Attribute.KeyGroup == nameof(Customer) &&
                                z.Attribute.Key == CustomerDefaultConfigs.DateOfBirthAttribute &&
                                z.Attribute.Value.Substring(8, 2) == dateOfBirthStr)
                    .Select(z => z.Customer);
            }
            else if (monthOfBirth > 0)
            {
                //only month is specified
                var dateOfBirthStr = "-" + monthOfBirth.ToString("00", CultureInfo.InvariantCulture) + "-";
                query = query
                    .Join(_gaRepository.Table, x => x.Id, y => y.EntityId, (x, y) => new { Customer = x, Attribute = y })
                    .Where(z => z.Attribute.KeyGroup == nameof(Customer) &&
                                z.Attribute.Key == CustomerDefaultConfigs.DateOfBirthAttribute &&
                                z.Attribute.Value.Contains(dateOfBirthStr))
                    .Select(z => z.Customer);
            }
            query = query.OrderByDescending(c => c.CreatedOnTimeUtc);

            var customers = new PagedList<Customer>(query, pageIndex, pageSize, getOnlyTotalCount);

            return customers;
        }

        /// <summary>
        /// Gets online customers
        /// </summary>
        /// <param name="lastActivityFromUtc">Customer last activity date (from)</param>
        /// <param name="customerRoleIds">A list of customer role identifiers to filter by (at least one match); pass null or empty list in order to load all customers; </param>
        /// <param name="pageIndex">Page index</param>
        /// <param name="pageSize">Page size</param>
        /// <returns>Customers</returns>
        public virtual IPagedList<Customer> GetOnlineCustomers(DateTime lastActivityFromUtc,
            int[] customerRoleIds, int pageIndex = 0, int pageSize = int.MaxValue)
        {
            var query = _customerRepository.Table;
            query = query.Where(c => lastActivityFromUtc <= c.LastActivityTimeUtc);
            query = query.Where(c => !c.IsDeleted);

            if (customerRoleIds != null && customerRoleIds.Length > 0)
                query = query.Where(c => _customerCustomerRoleMappingRepository.Table.Any(ccrm => ccrm.CustomerId == c.Id && customerRoleIds.Contains(ccrm.CustomerRoleId)));

            query = query.OrderByDescending(c => c.LastActivityTimeUtc);
            var customers = new PagedList<Customer>(query, pageIndex, pageSize);

            return customers;
        }

        /// <summary>
        /// Gets customers with shopping carts
        /// </summary>
        /// <param name="shoppingCartType">Shopping cart type; pass null to load all records</param>
        /// <param name="storeId">Store identifier; pass 0 to load all records</param>
        /// <param name="productId">Product identifier; pass null to load all records</param>
        /// <param name="createdFromUtc">Created date from (UTC); pass null to load all records</param>
        /// <param name="createdToUtc">Created date to (UTC); pass null to load all records</param>
        /// <param name="countryId">Billing country identifier; pass null to load all records</param>
        /// <param name="pageIndex">Page index</param>
        /// <param name="pageSize">Page size</param>
        /// <returns>Customers</returns>
        //public virtual IPagedList<Customer> GetCustomersWithShoppingCarts(ShoppingCartType? shoppingCartType = null,
        //    int storeId = 0, int? productId = null,
        //    DateTime? createdFromUtc = null, DateTime? createdToUtc = null, int? countryId = null,
        //    int pageIndex = 0, int pageSize = int.MaxValue)
        //{
        //    //get all shopping cart items
        //    var items = _shoppingCartRepository.Table;

        //    //filter by type
        //    if (shoppingCartType.HasValue)
        //        items = items.Where(item => item.ShoppingCartTypeId == (int)shoppingCartType.Value);

        //    //filter shopping cart items by store
        //    if (storeId > 0 && !_shoppingCartSettings.CartsSharedBetweenStores)
        //        items = items.Where(item => item.StoreId == storeId);

        //    //filter shopping cart items by product
        //    if (productId > 0)
        //        items = items.Where(item => item.ProductId == productId);

        //    //filter shopping cart items by date
        //    if (createdFromUtc.HasValue)
        //        items = items.Where(item => createdFromUtc.Value <= item.CreatedOnUtc);
        //    if (createdToUtc.HasValue)
        //        items = items.Where(item => createdToUtc.Value >= item.CreatedOnUtc);

        //    //get all active customers
        //    var customers = _customerRepository.Table.Where(customer => customer.Active && !customer.Deleted);

        //    //filter customers by billing country
        //    if (countryId > 0)
        //        customers = from c in customers
        //                    join a in _customerAddressRepository.Table on c.BillingAddressId equals a.Id
        //                    where a.CountryId == countryId
        //                    select c;

        //    var customersWithCarts = from c in customers
        //                             join item in items on c.Id equals item.CustomerId
        //                             orderby c.Id
        //                             select c;

        //    return new PagedList<Customer>(customersWithCarts.Distinct(), pageIndex, pageSize);
        //}

        /// <summary>
        /// Gets customer for shopping cart
        /// </summary>
        /// <param name="shoppingCart">Shopping cart</param>
        /// <returns>Result</returns>
        //public virtual Customer GetShoppingCartCustomer(IList<ShoppingCartItem> shoppingCart)
        //{
        //    var customerId = shoppingCart.FirstOrDefault()?.CustomerId;

        //    return customerId.HasValue && customerId != 0 ? GetCustomerById(customerId.Value) : null;
        //}

        /// <summary>
        /// Gets a value indicating whether customer is registered
        /// 根据CustomerSettings.UserRegistrationType判断用户注册类型
        /// 1: 已提交注册资料但未分配Guest类型角色的用户，等待邮件验证或管理员审核
        /// </summary>
        /// <param name="customer">Customer</param>
        /// <param name="onlyActiveCustomerRoles">A value indicating whether we should look only in active customer roles</param>
        /// <returns>Result</returns>
        public virtual bool IsRegistered(Customer customer, bool onlyActiveCustomerRoles = true)
        {
            if (customer == null) return false;
            return customer != null && IsInCustomerRole(customer, RoleType.Guests, onlyActiveCustomerRoles);
        }
        /// <summary>
        /// Delete a customer
        /// </summary>
        /// <param name="customer">Customer</param>
        public virtual void DeleteCustomer(Customer customer)
        {
            if (customer == null)
                throw new ArgumentNullException(nameof(customer));

            if (IsBuildInTaskUser(customer))
                throw new PMSException($"BuildIn Schedule Task account ({customer.LoginName}) could not be deleted", SystemLogLevel.Warning);

            customer.IsDeleted = true;
            //IsActive只用于销定账号，不用于注销
            //customer.IsActive = false;
            if (!string.IsNullOrEmpty(customer.Email))
                customer.Email += "-DELETED";
            if (!string.IsNullOrEmpty(customer.LoginName))
                customer.LoginName += "-DELETED";
            if (!string.IsNullOrEmpty(customer.Mobile))
                customer.Mobile += "-DELETED";

            UpdateCustomer(customer);

            //Set CustomerPassword InActive
            var customerPassword = GetCurrentPassword(customer.Id);
            if(customerPassword != null)
            {
                customerPassword.IsActive = false;
                UpdateCustomerPassword(customerPassword);
            }
            //event notification
            _eventDispatcher.EntityDeleted(customer);
        }

        /// <summary>
        /// Gets a customer
        /// </summary>
        /// <param name="customerId">Customer identifier</param>
        /// <returns>A customer</returns>
        public virtual Customer GetCustomerById(int customerId)
        {
            if (customerId == 0)
                return null;

            return _customerRepository.ToCachedGetById(customerId, _cachingSettings.ShortTermCacheTime);
        }

        /// <summary>
        /// Get customers by identifiers
        /// </summary>
        /// <param name="customerIds">Customer identifiers</param>
        /// <returns>Customers</returns>
        public virtual IList<Customer> GetCustomersByIds(int[] customerIds)
        {
            if (customerIds == null || customerIds.Length == 0)
                return new List<Customer>();

            var query = from c in _customerRepository.Table
                        where customerIds.Contains(c.Id) && !c.IsDeleted
                        select c;
            var customers = query.ToList();
            //sort by passed identifiers
            var sortedCustomers = new List<Customer>();
            foreach (var id in customerIds)
            {
                var customer = customers.Find(x => x.Id == id);
                if (customer != null)
                    sortedCustomers.Add(customer);
            }

            return sortedCustomers;
        }

        /// <summary>
        /// Gets a customer by GUID
        /// </summary>
        /// <param name="customerGuid">Customer GUID</param>
        /// <returns>A customer</returns>
        public virtual Customer GetCustomerByGuid(Guid customerGuid)
        {
            if (customerGuid == Guid.Empty)
                return null;

            var query = from c in _customerRepository.Table
                        where c.CustomerGuid == customerGuid
                        orderby c.Id
                        select c;
            var customer = query.FirstOrDefault();

            return customer;
        }

        /// <summary>
        /// Get customer by email
        /// </summary>
        /// <param name="email">Email</param>
        /// <returns>Customer</returns>
        public virtual Customer GetCustomerByEmail(string email)
        {
            if (string.IsNullOrWhiteSpace(email))
                return null;

            var query = from c in _customerRepository.Table
                        orderby c.Id
                        where c.Email == email
                        select c;
            var customer = query.FirstOrDefault();
            return customer;
        }

        /// <summary>
        /// Get customer by Username(Loginname)
        /// </summary>
        /// <param name="systemName">System name</param>
        /// <returns>Customer</returns>
        public virtual Customer GetCustomerByUsername(string username)
        {
            if (string.IsNullOrWhiteSpace(username))
                return null;

            var query = from c in _customerRepository.Table
                        orderby c.Id
                        where c.LoginName == username
                        select c;
            var customer = query.FirstOrDefault();
            return customer;
        }

        public virtual Customer GetCustomerByMobile(string phonenumber)
        {
            if (string.IsNullOrWhiteSpace(phonenumber))
                return null;

            var query = from c in _customerRepository.Table
                        orderby c.Id
                        where c.Mobile == phonenumber
                        select c;
            var customer = query.FirstOrDefault();
            return customer;
        }

        /// <summary>
        /// Gets built-in system record used for background tasks
        /// </summary>
        /// <returns>A customer object</returns>
        public virtual Customer GetOrCreateBackgroundTaskUser()
        {
            var backgroundTaskUser = GetCustomerByUsername(CustomerDefaultConfigs.BackgroundTaskCustomerName);

            if (backgroundTaskUser is null)
            {
                DateTime Now = DateTime.UtcNow;
                //If for any reason the system user isn't in the database, then we add it
                backgroundTaskUser = new Customer
                {
                    Email = "builtin@background-task-record.com",
                    LoginName = CustomerDefaultConfigs.BackgroundTaskCustomerName,
                    Mobile = "13300000000",
                    CustomerGuid = Guid.NewGuid(),
                    NickName = "BuiltInSystemAccountForBackgroundTasks",
                    IsActive = true,
                    IsDeleted = false,
                    CreatedOnTimeUtc = Now,
                    UpdatedOnTimeUtc = Now,
                    LastActivityTimeUtc = Now,
                    RegisteredInStoreId = _storeContext.CurrentStore.Id,
                    LastLoginIpAddress = "127.0.0.1"
                };

                InsertCustomer(backgroundTaskUser);

                var BuildInTaskRole = GetCustomerRoleByRoleType(RoleType.BuildInTaskUser);

                if (BuildInTaskRole is null)
                    throw new PMSException("'BuildInTaskUser' role could not be loaded", SystemLogLevel.Error);

                AddCustomerRoleMapping(new CustomerCustomerRoleMapping { CustomerRoleId = BuildInTaskRole.Id, CustomerId = backgroundTaskUser.Id });
            }

            return backgroundTaskUser;
        }

        /// <summary>
        /// Insert a customer
        /// </summary>
        /// <param name="customer">Customer</param>
        public virtual void InsertCustomer(Customer customer)
        {
            if (customer == null)
                throw new ArgumentNullException(nameof(customer));

            customer.CreatedOnTimeUtc = customer.UpdatedOnTimeUtc = customer.LastActivityTimeUtc = DateTime.UtcNow;
            _customerRepository.Insert(customer);

            //event notification
            _eventDispatcher.EntityInserted(customer);
        }

        /// <summary>
        /// Updates the customer
        /// </summary>
        /// <param name="customer">Customer</param>
        public virtual void UpdateCustomer(Customer customer)
        {
            if (customer == null)
                throw new ArgumentNullException(nameof(customer));

            customer.UpdatedOnTimeUtc = DateTime.UtcNow;
            _customerRepository.Update(customer);

            //event notification
            _eventDispatcher.EntityUpdated(customer);
        }

        #endregion

        #region Customer roles

        /// <summary>
        /// Add a customer-customer role mapping
        /// </summary>
        /// <param name="roleMapping">Customer-customer role mapping</param>
        public void AddCustomerRoleMapping(CustomerCustomerRoleMapping roleMapping)
        {
            if (roleMapping is null)
                throw new ArgumentNullException(nameof(roleMapping));

            _customerCustomerRoleMappingRepository.Insert(roleMapping);

            _eventDispatcher.EntityInserted(roleMapping);
        }

        /// <summary>
        /// Remove a customer-customer role mapping
        /// </summary>
        /// <param name="customer">Customer</param>
        /// <param name="role">Customer role</param>
        public void RemoveCustomerRoleMapping(Customer customer, CustomerRole role)
        {
            if (customer is null)
                throw new ArgumentNullException(nameof(customer));

            if (role is null)
                throw new ArgumentNullException(nameof(role));

            var mapping = _customerCustomerRoleMappingRepository.Table.SingleOrDefault(ccrm => ccrm.CustomerId == customer.Id && ccrm.CustomerRoleId == role.Id);

            if (mapping != null)
            {
                _customerCustomerRoleMappingRepository.Delete(mapping);

                //event notification
                _eventDispatcher.EntityDeleted(mapping);
            }
        }

        /// <summary>
        /// Delete a customer role
        /// </summary>
        /// <param name="customerRole">Customer role</param>
        public virtual void DeleteCustomerRole(CustomerRole customerRole)
        {
            if (customerRole == null)
                throw new ArgumentNullException(nameof(customerRole));

            if (customerRole.RoleType == RoleType.BuildInTaskUser ||
                customerRole.RoleType == RoleType.Guests ||
                customerRole.RoleType == RoleType.Administrator)
                throw new PMSException("Built-In role could not be deleted", SystemLogLevel.Warning);

            _customerRoleRepository.Delete(customerRole);

            //event notification
            _eventDispatcher.EntityDeleted(customerRole);
        }

        /// <summary>
        /// Gets a customer role
        /// </summary>
        /// <param name="customerRoleId">Customer role identifier</param>
        /// <returns>Customer role</returns>
        public virtual CustomerRole GetCustomerRoleById(int customerRoleId)
        {
            if (customerRoleId == 0)
                return null;

            return _customerRoleRepository.ToCachedGetById(customerRoleId);
        }

        /// <summary>
        /// Gets a customer role By roletype
        /// </summary>
        /// <param name="roleType">Customer RoleType</param>
        /// <returns>Customer role</returns>
        public virtual CustomerRole GetCustomerRoleByRoleType(RoleType roleType)
        {
            var key = _cacheKeyService.PrepareKeyForDefaultCache(CacheKeyConfigs.CustomerRolesByRoleTypeCacheKey, Enum.GetName(typeof(RoleType), roleType));

            var query = from cr in _customerRoleRepository.Table
                        orderby cr.Id
                        where cr.RoleType == roleType
                        select cr;
            var customerRole = query.ToCachedFirstOrDefault(key);

            return customerRole;
        }

        /// <summary>
        /// Get customer role identifiers
        /// </summary>
        /// <param name="customer">Customer</param>
        /// <param name="showHidden">A value indicating whether to load hidden records</param>
        /// <returns>Customer role identifiers</returns>
        public virtual int[] GetCustomerRoleIds(Customer customer, bool showHidden = false)
        {
            if (customer == null)
                throw new ArgumentNullException(nameof(customer));

            var query = from cr in _customerRoleRepository.Table
                        join crm in _customerCustomerRoleMappingRepository.Table on cr.Id equals crm.CustomerRoleId
                        where crm.CustomerId == customer.Id &&
                        (showHidden || cr.IsActive)
                        select cr.Id;

            var key = _cacheKeyService.PrepareKeyForShortTermCache(CacheKeyConfigs.CustomerRoleIdsCacheKey, customer, showHidden);

            return _staticCacheManager.Get(key, () => query.ToArray());
        }

        /// <summary>
        /// Gets list of customer roles
        /// </summary>
        /// <param name="customer">Customer</param>
        /// <param name="showHidden">A value indicating whether to load hidden records</param>
        /// <returns>Result</returns>
        public virtual IList<CustomerRole> GetCustomerRoles(Customer customer, bool showHidden = false)
        {
            if (customer == null)
                throw new ArgumentNullException(nameof(customer));

            var query = from cr in _customerRoleRepository.Table
                        join crm in _customerCustomerRoleMappingRepository.Table on cr.Id equals crm.CustomerRoleId
                        where crm.CustomerId == customer.Id &&
                        (showHidden || cr.IsActive)
                        select cr;

            var key = _cacheKeyService.PrepareKeyForShortTermCache(CacheKeyConfigs.CustomerRolesCacheKey, customer, showHidden);

            return _staticCacheManager.Get(key, () => query.ToList());
        }

        /// <summary>
        /// Gets all customer roles
        /// </summary>
        /// <param name="showHidden">A value indicating whether to show hidden records</param>
        /// <returns>Customer roles</returns>
        public virtual IList<CustomerRole> GetAllCustomerRoles(bool showHidden = false)
        {
            var key = _cacheKeyService.PrepareKeyForDefaultCache(CacheKeyConfigs.CustomerRolesAllCacheKey, showHidden);

            var query = from cr in _customerRoleRepository.Table
                        orderby cr.Name
                        where showHidden || cr.IsActive
                        select cr;

            var customerRoles = query.ToCachedList(key);

            return customerRoles;
        }

        /// <summary>
        /// Inserts a customer role
        /// </summary>
        /// <param name="customerRole">Customer role</param>
        public virtual void InsertCustomerRole(CustomerRole customerRole)
        {
            if (customerRole == null)
                throw new ArgumentNullException(nameof(customerRole));

            _customerRoleRepository.Insert(customerRole);

            //event notification
            _eventDispatcher.EntityInserted(customerRole);
        }

        /// <summary>
        /// Gets a value indicating whether customer is in a certain customer role
        /// </summary>
        /// <param name="customer">Customer</param>
        /// <param name="roleType">Customer role type</param>
        /// <param name="onlyActiveCustomerRoles">A value indicating whether we should look only in active customer roles</param>
        /// <returns>Result</returns>
        public virtual bool IsInCustomerRole(Customer customer,
            RoleType roleType, bool onlyActiveCustomerRoles = true)
        {
            if (customer == null)
                throw new ArgumentNullException(nameof(customer));

            var customerRoles = GetCustomerRoles(customer, !onlyActiveCustomerRoles);

            return customerRoles?.Any(cr => cr.RoleType == roleType) ?? false;
        }

        /// <summary>
        /// Gets a value indicating whether customer is administrator
        /// </summary>
        /// <param name="customer">Customer</param>
        /// <param name="onlyActiveCustomerRoles">A value indicating whether we should look only in active customer roles</param>
        /// <returns>Result</returns>
        public virtual bool IsAdmin(Customer customer, bool onlyActiveCustomerRoles = true)
        {
            return IsInCustomerRole(customer, RoleType.Administrator, onlyActiveCustomerRoles);
        }

        /// <summary>
        /// Gets a value indicating whether customer is BuildIn Backend Task User for Schedule Task 
        /// </summary>
        /// <param name="customer">Customer</param>
        /// <returns>Result</returns>
        public virtual bool IsBuildInTaskUser(Customer customer)
        {
            return IsInCustomerRole(customer, RoleType.BuildInTaskUser, true);
        }

        /// <summary>
        /// Gets a value indicating whether customer is guest
        /// </summary>
        /// <param name="customer">Customer</param>
        /// <param name="onlyActiveCustomerRoles">A value indicating whether we should look only in active customer roles</param>
        /// <returns>Result</returns>
        public virtual bool IsGuest(Customer customer, bool onlyActiveCustomerRoles = true)
        {
            return IsInCustomerRole(customer, RoleType.Guests, onlyActiveCustomerRoles);
        }

        /// <summary>
        /// Updates the customer role
        /// </summary>
        /// <param name="customerRole">Customer role</param>
        public virtual void UpdateCustomerRole(CustomerRole customerRole)
        {
            if (customerRole == null)
                throw new ArgumentNullException(nameof(customerRole));

            _customerRoleRepository.Update(customerRole);

            //event notification
            _eventDispatcher.EntityUpdated(customerRole);
        }

        #endregion

        #region Customer passwords

        /// <summary>
        /// Get current customer password
        /// </summary>
        /// <param name="customerId">Customer identifier</param>
        /// <returns>Customer password</returns>
        public virtual CustomerPassword GetCurrentPassword(int customerId)
        {
            if (customerId == 0)
                return null;
            return _customerPasswordRepository.Table.FirstOrDefault(password => password.IsActive && password.CustomerId == customerId);
        }

        /// <summary>
        /// Insert a customer password
        /// </summary>
        /// <param name="customerPassword">Customer password</param>
        public virtual void InsertCustomerPassword(CustomerPassword customerPassword)
        {
            if (customerPassword == null)
                throw new ArgumentNullException(nameof(customerPassword));

            customerPassword.CreatedOnTimeUtc = customerPassword.UpdatedOnTimeUtc = DateTime.UtcNow;
            _customerPasswordRepository.Insert(customerPassword);

            //event notification
            _eventDispatcher.EntityInserted(customerPassword);
        }

        /// <summary>
        /// Update a customer password
        /// </summary>
        /// <param name="customerPassword">Customer password</param>
        public virtual void UpdateCustomerPassword(CustomerPassword customerPassword)
        {
            if (customerPassword == null)
                throw new ArgumentNullException(nameof(customerPassword));

            customerPassword.UpdatedOnTimeUtc = DateTime.UtcNow;
            _customerPasswordRepository.Update(customerPassword);

            //event notification
            _eventDispatcher.EntityUpdated(customerPassword);
        }

        /// <summary>
        /// Check whether password recovery token is valid
        /// </summary>
        /// <param name="customer">Customer</param>
        /// <param name="token">Token to validate</param>
        /// <returns>Result</returns>
        public virtual bool IsPasswordRecoveryTokenValid(Customer customer, string token)
        {
            if (customer == null)
                throw new ArgumentNullException(nameof(customer));

            var cPrt = _genericAttributeService.GetAttribute<string>(customer, CustomerDefaultConfigs.PasswordRecoveryTokenAttribute);
            if (string.IsNullOrEmpty(cPrt))
                return false;

            if (!cPrt.Equals(token, StringComparison.InvariantCultureIgnoreCase))
                return false;

            return true;
        }

        /// <summary>
        /// Check whether password recovery link is expired
        /// </summary>
        /// <param name="customer">Customer</param>
        /// <returns>Result</returns>
        public virtual bool IsPasswordRecoveryLinkExpired(Customer customer)
        {
            if (customer == null)
                throw new ArgumentNullException(nameof(customer));

            if (_customerSettings.PasswordRecoveryLinkDaysValid == 0)
                return false;

            var geneatedDate = _genericAttributeService.GetAttribute<DateTime?>(customer, CustomerDefaultConfigs.PasswordRecoveryTokenDateGeneratedAttribute);
            if (!geneatedDate.HasValue)
                return false;

            var daysPassed = (DateTime.UtcNow - geneatedDate.Value).TotalDays;
            if (daysPassed > _customerSettings.PasswordRecoveryLinkDaysValid)
                return true;

            return false;
        }

        /// <summary>
        /// Check whether customer password is expired 
        /// DavidLee 更改检查密码过期检查机制，判断密码过期时效，返回密码过期天数。
        /// 在容错期时间段内，只提醒用户更改密码，在容错期后强制要求用户更改密码
        /// </summary>
        /// <param name="customer">Customer</param>
        /// <returns>expired days if password is expired; otherwise 0</returns>
        public virtual int PasswordIsExpired(Customer customer)
        {
            if (customer == null)
                throw new ArgumentNullException(nameof(customer));

            //password lifetime is disabled for user
            if (!GetCustomerRoles(customer).Any(role => role.IsActive && role.EnablePasswordLifetime))
                return 0;

            //setting disabled for all
            if (_customerSettings.PasswordLifetime == 0)
                return 0;

            var cacheKey = _cacheKeyService.PrepareKeyForShortTermCache(CacheKeyConfigs.CustomerPasswordLifetimeCacheKey, customer);

            //get current password usage time
            var currentLifetime = _staticCacheManager.Get(cacheKey, () =>
            {
                var customerPassword = GetCurrentPassword(customer.Id);
                //password is not found, so return max value to force customer to change password
                if (customerPassword == null)
                    return int.MaxValue;

                return (DateTime.UtcNow - customerPassword.UpdatedOnTimeUtc).Days;
            });

            int expiredDays = currentLifetime - _customerSettings.PasswordLifetime;
            return expiredDays <= 0 ? 0 : expiredDays;
        }

        #endregion

        //#region Customer address mapping

        ///// <summary>
        ///// Remove a customer-address mapping record
        ///// </summary>
        ///// <param name="customer">Customer</param>
        ///// <param name="address">Address</param>
        //public virtual void RemoveCustomerAddress(Customer customer, Address address)
        //{
        //    if (customer == null)
        //        throw new ArgumentNullException(nameof(customer));

        //    if (_customerAddressMappingRepository.Table.FirstOrDefault(m => m.AddressId == address.Id && m.CustomerId == customer.Id) is CustomerAddressMapping mapping)
        //    {
        //        if (customer.BillingAddressId == address.Id)
        //            customer.BillingAddressId = null;
        //        if (customer.ShippingAddressId == address.Id)
        //            customer.ShippingAddressId = null;

        //        _customerAddressMappingRepository.Delete(mapping);

        //        //event notification
        //        _eventPublisher.EntityDeleted(mapping);
        //    }
        //}

        ///// <summary>
        ///// Inserts a customer-address mapping record
        ///// </summary>
        ///// <param name="customer">Customer</param>
        ///// <param name="address">Address</param>
        //public virtual void InsertCustomerAddress(Customer customer, Address address)
        //{
        //    if (customer is null)
        //        throw new ArgumentNullException(nameof(customer));

        //    if (address is null)
        //        throw new ArgumentNullException(nameof(address));

        //    if (_customerAddressMappingRepository.Table.FirstOrDefault(m => m.AddressId == address.Id && m.CustomerId == customer.Id) is null)
        //    {
        //        var mapping = new CustomerAddressMapping
        //        {
        //            AddressId = address.Id,
        //            CustomerId = customer.Id
        //        };

        //        _customerAddressMappingRepository.Insert(mapping);

        //        //event notification
        //        _eventPublisher.EntityInserted(mapping);
        //    }
        //}

        ///// <summary>
        ///// Gets a list of addresses mapped to customer
        ///// </summary>
        ///// <param name="customerId">Customer identifier</param>
        ///// <returns>Result</returns>
        //public virtual IList<Address> GetAddressesByCustomerId(int customerId)
        //{
        //    var query = from address in _customerAddressRepository.Table
        //                join cam in _customerAddressMappingRepository.Table on address.Id equals cam.AddressId
        //                where cam.CustomerId == customerId
        //                select address;

        //    var key = _cacheKeyService.PrepareKeyForShortTermCache(NopCustomerServicesDefaults.CustomerAddressesByCustomerIdCacheKey, customerId);

        //    return _staticCacheManager.Get(key, () => query.ToList());
        //}

        ///// <summary>
        ///// Gets a address mapped to customer
        ///// </summary>
        ///// <param name="customerId">Customer identifier</param>
        ///// <param name="addressId">Address identifier</param>
        ///// <returns>Result</returns>
        //public virtual Address GetCustomerAddress(int customerId, int addressId)
        //{
        //    if (customerId == 0 || addressId == 0)
        //        return null;

        //    var query = from address in _customerAddressRepository.Table
        //                join cam in _customerAddressMappingRepository.Table on address.Id equals cam.AddressId
        //                where cam.CustomerId == customerId && address.Id == addressId
        //                select address;

        //    var key = _cacheKeyService.PrepareKeyForShortTermCache(NopCustomerServicesDefaults.CustomerAddressCacheKeyCacheKey, customerId, addressId);

        //    return _staticCacheManager.Get(key, () => query.Single());
        //}

        ///// <summary>
        ///// Gets a customer billing address
        ///// </summary>
        ///// <param name="customer">Customer identifier</param>
        ///// <returns>Result</returns>
        //public virtual Address GetCustomerBillingAddress(Customer customer)
        //{
        //    if (customer is null)
        //        throw new ArgumentNullException(nameof(customer));

        //    return GetCustomerAddress(customer.Id, customer.BillingAddressId ?? 0);
        //}

        ///// <summary>
        ///// Gets a customer shipping address
        ///// </summary>
        ///// <param name="customer">Customer</param>
        ///// <returns>Result</returns>
        //public virtual Address GetCustomerShippingAddress(Customer customer)
        //{
        //    if (customer is null)
        //        throw new ArgumentNullException(nameof(customer));

        //    return GetCustomerAddress(customer.Id, customer.ShippingAddressId ?? 0);
        //}

        //#endregion

        #region Organize
        /// <summary>
        /// Get All Organize List
        /// </summary>
        /// <returns></returns>
        public virtual IList<Organize> GetAllOrganizes()
        {
            var key = _cacheKeyService.PrepareKeyForDefaultCache(CacheKeyConfigs.CustomerOrganizesAllCacheKey);
            var query = from o in _organizeRepository.Table
                        where o.IsActive
                        orderby o.ParentId, o.DisplayOrder
                        select o;
            return query.ToCachedList(key);
        }
        #endregion

        #endregion
    }
}
